//
// (C) Copyright 2009 Irantha Suwandarathna (irantha@gmail.com)
// All rights reserved.
//

/* Copyright (c) 2001-2008, The HSQL Development Group
 * All rights reserved.
 *
 * Redistribution and use _in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer.
 *
 * Redistributions _in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer _in the documentation
 * and/or other materials provided with the distribution.
 *
 * Neither the name of the HSQL Development Group nor the names of its
 * contributors may be used to endorse or promote products derived from this
 * software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL HSQL DEVELOPMENT GROUP, HSQLDB.ORG,
 * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */


using System;
using System.Collections.Generic;
using System.Text;
using EffiProz.Core.Lib;
using EffiProz.Core.Indexes;
using EffiProz.Core.Persist;
using EffiProz.Core.RowIO;
using EffiProz.Core.Errors;
using EffiProz.Core.Rights;
using EffiProz.Core.Navigators;
using EffiProz.Core.DataTypes;
using EffiProz.Core.Results;


namespace EffiProz.Core
{

    /**
     * The methods _in this class perform alterations to the structure of an
     * existing table which may result _in a new Table object
     *
     * @author fredt@users
     * @version 1.8.0
     * @since 1.7.0
     */
    public class TableWorks
    {

        public OrderedHashSet emptySet = new OrderedHashSet();
        private Database database;
        private Table table;
        private Session session;

        public TableWorks(Session session, Table table)
        {

            this.database = table.database;
            this.table = table;
            this.session = session;
        }

        public Table getTable()
        {
            return table;
        }

        public void checkCreateForeignKey(Constraint c)
        {

            if (c.core.mainName == table.getName())
            {
                if (ArrayUtil.haveCommonElement(c.core.refCols, c.core.mainCols,
                                                c.core.refCols.Length))
                {
                    throw Error.error(ErrorCode.X_42527);
                }
            }

            // column defaults
            bool check =
                c.core.updateAction == SchemaObjectReferentialAction.SET_DEFAULT
                || c.core.deleteAction
                   == SchemaObjectReferentialAction.SET_DEFAULT;

            if (check)
            {
                for (int i = 0; i < c.core.refCols.Length; i++)
                {
                    ColumnSchema col = table.getColumn(c.core.refCols[i]);
                    Expression defExpr = col.getDefaultExpression();

                    if (defExpr == null)
                    {
                        String columnName = col.getName().statementName;

                        throw Error.error(ErrorCode.X_42521, columnName);
                    }
                }
            }

            check = c.core.updateAction == SchemaObjectReferentialAction.SET_NULL
                    || c.core.deleteAction
                       == SchemaObjectReferentialAction.SET_NULL;

            if (check)
            {
                for (int i = 0; i < c.core.refCols.Length; i++)
                {
                    ColumnSchema col = table.getColumn(c.core.refCols[i]);

                    if (!col.isNullable())
                    {
                        String columnName = col.getName().statementName;

                        throw Error.error(ErrorCode.X_42520, columnName);
                    }
                }
            }

            database.schemaManager.checkSchemaObjectNotExists(c.getName());

            // duplicate name check for a new table
            if (table.getConstraint(c.getName().name) != null)
            {
                throw Error.error(ErrorCode.X_42504, c.getName().statementName);
            }

            // existing FK check
            if (table.getFKConstraintForColumns(
                    c.core.mainTable, c.core.mainCols, c.core.refCols) != null)
            {
                throw Error.error(ErrorCode.X_42528, c.getName().statementName);
            }

            if (c.core.mainTable.isTemp() != table.isTemp())
            {
                throw Error.error(ErrorCode.X_42524, c.getName().statementName);
            }

            if (c.core.mainTable.getUniqueConstraintForColumns(
                    c.core.mainCols, c.core.refCols) == null)
            {
                throw Error.error(ErrorCode.X_42529,
                                  c.getMain().getName().statementName);
            }

            // check after UNIQUE check
            c.core.mainTable.checkColumnsMatch(c.core.mainCols, table,
                                               c.core.refCols);

            bool[] checkList =
                c.core.mainTable.getColumnCheckList(c.core.mainCols);

            //        Grantee   grantee   = table.getOwner();
            Grantee grantee = session.getGrantee();

            grantee.checkReferences(c.core.mainTable, checkList);
        }

        /**
         * Creates a foreign key on an existing table. Foreign keys are enforced by
         * indexes on both the referencing (child) and referenced (main) tables.
         *
         * <p> Since version 1.7.2, a unique constraint on the referenced columns
         * must exist. The non-unique index on the referencing table is now always
         * created whether or not a PK or unique constraint index on the columns
         * exist. Foriegn keys on temp tables can reference other temp tables with
         * the same rules above. Foreign keys on permanent tables cannot reference
         * temp tables. Duplicate foreign keys are now disallowed.
         *
         * @param c the constraint object
         */
        public void addForeignKey(Constraint c)
        {

            checkCreateForeignKey(c);

            Constraint uniqueConstraint =
                c.core.mainTable.getUniqueConstraintForColumns(c.core.mainCols,
                    c.core.refCols);
            Index mainIndex = uniqueConstraint.getMainIndex();

            uniqueConstraint.checkReferencedRows(session, table, c.core.refCols);

            int offset = database.schemaManager.getTableIndex(table);
            bool isForward = c.core.mainTable.getSchemaName()
                                != table.getSchemaName();

            if (offset != -1
                    && offset
                       < database.schemaManager.getTableIndex(c.core.mainTable))
            {
                isForward = true;
            }

            QNameManager.QName indexName = database.nameManager.newAutoName("IDX",
                table.getSchemaName(), table.getName(), SchemaObjectTypes.INDEX);
            Index refIndex = table.createIndexStructure(indexName, c.core.refCols,
                null, null, false, true, isForward);
            QNameManager.QName mainName = database.nameManager.newAutoName("REF",
                c.getName().name, table.getSchemaName(), table.getName(),
                SchemaObjectTypes.INDEX);

            c.core.uniqueName = uniqueConstraint.getName();
            c.core.mainName = mainName;
            c.core.mainIndex = mainIndex;
            c.core.refTable = table;
            c.core.refName = c.getName();
            c.core.refIndex = refIndex;
            c.isForward = isForward;

            Table tn = table.moveDefinition(session, table.tableType, null, c,
                                            refIndex, -1, 0, emptySet, emptySet);

            tn.moveData(session, table, -1, 0);
            c.core.mainTable.addConstraint(new Constraint(mainName, c));
            database.schemaManager.addSchemaObject(c);
            database.persistentStoreCollection.releaseStore(table);
            setNewTableInSchema(tn);
            updateConstraints(tn, emptySet);
            database.schemaManager.recompileDependentObjects(tn);

            table = tn;
        }

        /**
         * Checks if the attributes of the Column argument, c, are compatible with
         * the operation of adding such a Column to the Table argument, table.
         *
         * @param col the Column to add to the Table, t
         */
        public void checkAddColumn(ColumnSchema col)
        {

            if (table.isText() && !table.isEmpty(session))
            {
                throw Error.error(ErrorCode.X_S0521);
            }

            if (table.findColumn(col.getName().name) != -1)
            {
                throw Error.error(ErrorCode.X_42504);
            }

            if (col.isPrimaryKey() && table.hasPrimaryKey())
            {
                throw Error.error(ErrorCode.X_42530);
            }

            if (col.isIdentity() && table.hasIdentityColumn())
            {
                throw Error.error(ErrorCode.X_42525);
            }

            if (!table.isEmpty(session) && !col.hasDefault()
                    && (!col.isNullable() || col.isPrimaryKey())
                    && !col.isIdentity())
            {
                throw Error.error(ErrorCode.X_42531);
            }
        }

        public void addColumn(ColumnSchema column, int colIndex,
                       EfzArrayList constraints)
        {

            Index index = null;
            Table originalTable = table;
            Constraint mainConstraint = null;
            bool addFK = false;
            bool addUnique = false;
            bool addCheck = false;

            checkAddColumn(column);

            Constraint c = (Constraint)constraints.get(0);

            if (c.getConstraintType()
                    == SchemaObjectConstraintTypes.PRIMARY_KEY)
            {
                c.core.mainCols = new int[] { colIndex };

                database.schemaManager.checkSchemaObjectNotExists(c.getName());

                if (table.hasPrimaryKey())
                {
                    throw Error.error(ErrorCode.X_42530);
                }

                addUnique = true;
            }
            else
            {
                c = null;
            }

            table = table.moveDefinition(session, table.tableType, column, c,
                                         null, colIndex, 1, emptySet, emptySet);

            for (int i = 1; i < constraints.size(); i++)
            {
                c = (Constraint)constraints.get(i);

                switch (c.constType)
                {

                    case SchemaObjectConstraintTypes.UNIQUE:
                        {
                            if (addUnique)
                            {
                                throw Error.error(ErrorCode.X_42522);
                            }

                            addUnique = true;
                            c.core.mainCols = new int[] { colIndex };

                            database.schemaManager.checkSchemaObjectNotExists(
                                c.getName());

                            QNameManager.QName indexName =
                                database.nameManager.newAutoName("IDX",
                                                                 c.getName().name,
                                                                 table.getSchemaName(),
                                                                 table.getName(),
                                                                 SchemaObjectTypes.INDEX);

                            // create an autonamed index
                            index = table.createAndAddIndexStructure(indexName,
                                    c.getMainColumns(), null, null, true, true, false);
                            c.core.mainTable = table;
                            c.core.mainIndex = index;

                            table.addConstraint(c);

                            break;
                        }
                    case SchemaObjectConstraintTypes.FOREIGN_KEY:
                        {
                            if (addFK)
                            {
                                throw Error.error(ErrorCode.X_42528);
                            }

                            addFK = true;
                            c.core.refCols = new int[] { colIndex };

                            bool isSelf = originalTable == c.core.mainTable;

                            if (isSelf)
                            {
                                c.core.mainTable = table;
                            }

                            c.setColumnsIndexes(table);
                            checkCreateForeignKey(c);

                            Constraint uniqueConstraint =
                                c.core.mainTable.getUniqueConstraintForColumns(
                                    c.core.mainCols, c.core.refCols);
                            bool isForward = c.core.mainTable.getSchemaName()
                                                != table.getSchemaName();
                            int offset =
                                database.schemaManager.getTableIndex(originalTable);

                            if (!isSelf
                                    && offset
                                       < database.schemaManager.getTableIndex(
                                           c.core.mainTable))
                            {
                                isForward = true;
                            }

                            QNameManager.QName indexName =
                                database.nameManager.newAutoName("IDX",
                                                                 c.getName().name,
                                                                 table.getSchemaName(),
                                                                 table.getName(),
                                                                 SchemaObjectTypes.INDEX);

                            index = table.createAndAddIndexStructure(indexName,
                                    c.getRefColumns(), null, null, false, true,
                                    isForward);
                            c.core.uniqueName = uniqueConstraint.getName();
                            c.core.mainName = database.nameManager.newAutoName("REF",
                                    c.core.refName.name, table.getSchemaName(),
                                    table.getName(), SchemaObjectTypes.INDEX);
                            c.core.mainIndex = uniqueConstraint.getMainIndex();
                            c.core.refIndex = index;
                            c.isForward = isForward;

                            table.addConstraint(c);

                            mainConstraint = new Constraint(c.core.mainName, c);

                            break;
                        }
                    case SchemaObjectConstraintTypes.CHECK:
                        if (addCheck)
                        {
                            throw Error.error(ErrorCode.X_42528);
                        }

                        addCheck = true;

                        c.prepareCheckConstraint(session, table, false);
                        table.addConstraint(c);

                        if (c.isNotNull())
                        {
                            column.setNullable(false);
                            table.setColumnTypeVars(colIndex);
                        }
                        break;
                }
            }

            table.moveData(session, originalTable, colIndex, 1);

            if (mainConstraint != null)
            {
                mainConstraint.getMain().addConstraint(mainConstraint);
            }

            registerConstraintNames(constraints);
            setNewTableInSchema(table);
            updateConstraints(table, emptySet);
            database.persistentStoreCollection.releaseStore(originalTable);
            database.schemaManager.recompileDependentObjects(table);
        }

        public void updateConstraints(OrderedHashSet tableSet,
                               OrderedHashSet dropConstraints)
        {

            for (int i = 0; i < tableSet.size(); i++)
            {
                Table t = (Table)tableSet.get(i);

                updateConstraints(t, dropConstraints);
            }
        }

        public void updateConstraints(Table t, OrderedHashSet dropConstraints)
        {

            for (int i = t.constraintList.Length - 1; i >= 0; i--)
            {
                Constraint c = t.constraintList[i];

                if (dropConstraints.contains(c.getName()))
                {
                    t.removeConstraint(i);

                    continue;
                }

                if (c.getConstraintType()
                        == SchemaObjectConstraintTypes.FOREIGN_KEY)
                {
                    Table mainT = database.schemaManager.getUserTable(session,
                        c.core.mainTable.getName());
                    Constraint mainC = mainT.getConstraint(c.getMainName().name);

                    mainC.core = c.core;
                }
                else if (c.getConstraintType()
                         == SchemaObjectConstraintTypes.MAIN)
                {
                    Table refT = database.schemaManager.getUserTable(session,
                        c.core.refTable.getName());
                    Constraint refC = refT.getConstraint(c.getRefName().name);

                    refC.core = c.core;
                }
            }
        }

        public OrderedHashSet makeNewTables(OrderedHashSet tableSet,
                                     OrderedHashSet dropConstraintSet,
                                     OrderedHashSet dropIndexSet)
        {

            OrderedHashSet newSet = new OrderedHashSet();

            for (int i = 0; i < tableSet.size(); i++)
            {
                Table t = (Table)tableSet.get(i);
                TableWorks tw = new TableWorks(session, t);

                tw.makeNewTable(dropConstraintSet, dropIndexSet);
                newSet.add(tw.getTable());
            }

            return newSet;
        }

        /**
         * Drops constriants and their indexes in table. Uses set of names.
         */
        public void makeNewTable(OrderedHashSet dropConstraintSet,
                          OrderedHashSet dropIndexSet)
        {

            Table tn = table.moveDefinition(session, table.tableType, null, null,
                                            null, -1, 0, dropConstraintSet,
                                            dropIndexSet);

            if (tn.indexList.Length == table.indexList.Length)
            {
                database.persistentStoreCollection.releaseStore(tn);

                return;
            }

            tn.moveData(session, table, -1, 0);
            database.persistentStoreCollection.releaseStore(table);

            table = tn;
        }

        /**
         * Because of the way indexes and column data are held in memory and on
         * disk, it is necessary to recreate the table when an index is added to a
         * non-empty cached table.
         *
         * <p> With empty tables, Index objects are simply added
         *
         * <p> With MEOMRY and TEXT tables, a new index is built up and nodes for
         * earch row are interlinked (fredt@users)
         *
         * @param col int[]
         * @param name HsqlName
         * @param unique bool
         * @return new index
         */
        public Index addIndex(int[] col, QNameManager.QName name, bool unique)
        {

            Index newindex;

            if (table.isEmpty(session) || table.isIndexingMutable())
            {
                PersistentStore store = session.sessionData.getRowStore(table);

                newindex = table.createIndex(name, col, null, null, unique, false,
                                             false);
            }
            else
            {
                newindex = table.createIndexStructure(name, col, null, null,
                                                      unique, false, false);

                Table tn = table.moveDefinition(session, table.tableType, null,
                                                null, newindex, -1, 0, emptySet,
                                                emptySet);

                // for all sessions move the data
                tn.moveData(session, table, -1, 0);
                database.persistentStoreCollection.releaseStore(table);

                table = tn;

                setNewTableInSchema(table);
                updateConstraints(table, emptySet);
            }

            database.schemaManager.addSchemaObject(newindex);
            database.schemaManager.recompileDependentObjects(table);

            return newindex;
        }

        public void addPrimaryKey(Constraint constraint, QNameManager.QName name)
        {

            if (table.hasPrimaryKey())
            {
                throw Error.error(ErrorCode.X_42532);
            }

            database.schemaManager.checkSchemaObjectNotExists(name);

            Table tn = table.moveDefinition(session, table.tableType, null,
                                            constraint, null, -1, 0, emptySet,
                                            emptySet);

            tn.moveData(session, table, -1, 0);
            database.persistentStoreCollection.releaseStore(table);

            table = tn;

            database.schemaManager.addSchemaObject(constraint);
            setNewTableInSchema(table);
            updateConstraints(table, emptySet);
            database.schemaManager.recompileDependentObjects(table);
        }

        /**
         * A unique constraint relies on a unique indexe on the table. It can cover
         * a single column or multiple columns.
         *
         * <p> All unique constraint names are generated by Database.java as unique
         * within the database. Duplicate constraints (more than one unique
         * constriant on the same set of columns) are not allowed. (fredt@users)
         *
         * @param cols int[]
         * @param name HsqlName
         */
        public void addUniqueConstraint(int[] cols, QNameManager.QName name)
        {

            database.schemaManager.checkSchemaObjectNotExists(name);

            if (table.getUniqueConstraintForColumns(cols) != null)
            {
                throw Error.error(ErrorCode.X_42522);
            }

            // create an autonamed index
            QNameManager.QName indexname = database.nameManager.newAutoName("IDX",
                name.name, table.getSchemaName(), table.getName(),
                SchemaObjectTypes.INDEX);
            Index index = table.createIndexStructure(indexname, cols, null, null,
                true, true, false);
            Constraint constraint =
                new Constraint(name, table, index,
                               SchemaObjectConstraintTypes.UNIQUE);
            Table tn = table.moveDefinition(session, table.tableType, null,
                                            constraint, index, -1, 0, emptySet,
                                            emptySet);

            tn.moveData(session, table, -1, 0);
            database.persistentStoreCollection.releaseStore(table);

            table = tn;

            database.schemaManager.addSchemaObject(constraint);
            setNewTableInSchema(table);
            updateConstraints(table, emptySet);
            database.schemaManager.recompileDependentObjects(table);
        }

        public void addCheckConstraint(Constraint c)
        {

            database.schemaManager.checkSchemaObjectNotExists(c.getName());
            c.prepareCheckConstraint(session, table, true);
            table.addConstraint(c);

            if (c.isNotNull())
            {
                ColumnSchema column = table.getColumn(c.notNullColumnIndex);

                column.setNullable(false);
                table.setColumnTypeVars(c.notNullColumnIndex);
            }

            database.schemaManager.addSchemaObject(c);
        }

        /**
         * Because of the way indexes and column data are held in memory and on
         * disk, it is necessary to recreate the table when an index is added to or
         * removed from a non-empty table.
         *
         * <p> Originally, this method would break existing foreign keys as the
         * table order in the DB was changed. The new table is now linked in place
         * of the old table (fredt@users)
         *
         * @param indexName String
         */
        public void dropIndex(String indexName)
        {

            Index index;

            index = table.getIndex(indexName);

            if (table.isIndexingMutable())
            {
                table.dropIndex(session, indexName);
            }
            else
            {
                OrderedHashSet indexSet = new OrderedHashSet();

                indexSet.add(table.getIndex(indexName).getName());

                Table tn = table.moveDefinition(session, table.tableType, null,
                                                null, null, -1, 0, emptySet,
                                                indexSet);

                tn.moveData(session, table, -1, 0);
                updateConstraints(tn, emptySet);
                setNewTableInSchema(tn);
                database.persistentStoreCollection.releaseStore(table);

                table = tn;
            }

            if (!index.isConstraint())
            {
                database.schemaManager.removeSchemaObject(index.getName());
            }

            database.schemaManager.recompileDependentObjects(table);
        }

        public void dropColumn(int colIndex, bool cascade)
        {

            OrderedHashSet constraintNameSet = new OrderedHashSet();
            OrderedHashSet dependentConstraints =
                table.getDependentConstraints(colIndex);
            OrderedHashSet cascadingConstraints =
                table.getContainingConstraints(colIndex);
            OrderedHashSet indexNameSet = table.getContainingIndexNames(colIndex);
            QNameManager.QName columnName = table.getColumn(colIndex).getName();
            OrderedHashSet referencingObjects =
                database.schemaManager.getReferencingObjects(table.getName(),
                    columnName);

            if (table.isText() && !table.isEmpty(session))
            {
                throw Error.error(ErrorCode.X_S0521);
            }

            if (!cascade)
            {
                if (!cascadingConstraints.isEmpty())
                {
                    Constraint c = (Constraint)cascadingConstraints.get(0);
                    QNameManager.QName name = c.getName();

                    throw Error.error(ErrorCode.X_42536,
                                      name.getSchemaQualifiedStatementName());
                }

                if (!referencingObjects.isEmpty())
                {
                   
                    if(referencingObjects.size() >0)  //for (int i = 0; i < referencingObjects.size(); i++)
                    {
                        QNameManager.QName name = (QNameManager.QName)referencingObjects.get(0);

                        throw Error.error(ErrorCode.X_42536,
                                          name.getSchemaQualifiedStatementName());
                    }
                }
            }

            dependentConstraints.addAll(cascadingConstraints);
            cascadingConstraints.clear();

            OrderedHashSet tableSet = new OrderedHashSet();

            for (int i = 0; i < dependentConstraints.size(); i++)
            {
                Constraint c = (Constraint)dependentConstraints.get(i);

                if (c.constType == SchemaObjectConstraintTypes.FOREIGN_KEY)
                {
                    tableSet.add(c.getMain());
                    constraintNameSet.add(c.getMainName());
                    constraintNameSet.add(c.getRefName());
                    indexNameSet.add(c.getRefIndex().getName());
                }

                if (c.constType == SchemaObjectConstraintTypes.MAIN)
                {
                    tableSet.add(c.getRef());
                    constraintNameSet.add(c.getMainName());
                    constraintNameSet.add(c.getRefName());
                    indexNameSet.add(c.getRefIndex().getName());
                }

                constraintNameSet.add(c.getName());
            }

            tableSet = makeNewTables(tableSet, constraintNameSet, indexNameSet);

            Table tn = table.moveDefinition(session, table.tableType, null, null,
                                            null, colIndex, -1, constraintNameSet,
                                            indexNameSet);

            tn.moveData(session, table, colIndex, -1);

            //
            database.schemaManager.removeSchemaObjects(referencingObjects);
            database.schemaManager.removeSchemaObjects(constraintNameSet);
            setNewTableInSchema(tn);
            setNewTablesInSchema(tableSet);
            updateConstraints(tn, emptySet);
            updateConstraints(tableSet, constraintNameSet);
            database.persistentStoreCollection.releaseStore(table);
            database.schemaManager.recompileDependentObjects(tableSet);
            database.schemaManager.recompileDependentObjects(tn);

            table = tn;
        }

        public void registerConstraintNames(EfzArrayList constraints)
        {

            for (int i = 0; i < constraints.size(); i++)
            {
                Constraint c = (Constraint)constraints.get(i);

                switch (c.constType)
                {

                    case SchemaObjectConstraintTypes.PRIMARY_KEY:
                    case SchemaObjectConstraintTypes.UNIQUE:
                    case SchemaObjectConstraintTypes.CHECK:
                        database.schemaManager.addSchemaObject(c);
                        break;
                }
            }
        }

        public void dropConstraint(String name, bool cascade)
        {

            Constraint constraint = table.getConstraint(name);

            if (constraint == null)
            {
                throw Error.error(ErrorCode.X_42501, name);
            }

            switch (constraint.getConstraintType())
            {

                case SchemaObjectConstraintTypes.MAIN:
                    throw Error.error(ErrorCode.X_28502);
                case SchemaObjectConstraintTypes.PRIMARY_KEY:
                case SchemaObjectConstraintTypes.UNIQUE:
                    {
                        OrderedHashSet dependentConstraints =
                            table.getDependentConstraints(constraint);

                        // throw if unique constraint is referenced by foreign key
                        if (!cascade && !dependentConstraints.isEmpty())
                        {
                            Constraint c = (Constraint)dependentConstraints.get(0);

                            throw Error.error(
                                ErrorCode.X_42533,
                                c.getName().getSchemaQualifiedStatementName());
                        }

                        OrderedHashSet tableSet = new OrderedHashSet();
                        OrderedHashSet constraintNameSet = new OrderedHashSet();
                        OrderedHashSet indexNameSet = new OrderedHashSet();

                        for (int i = 0; i < dependentConstraints.size(); i++)
                        {
                            Constraint c = (Constraint)dependentConstraints.get(i);
                            Table t = c.getMain();

                            if (t != table)
                            {
                                tableSet.add(t);
                            }

                            t = c.getRef();

                            if (t != table)
                            {
                                tableSet.add(t);
                            }

                            constraintNameSet.add(c.getMainName());
                            constraintNameSet.add(c.getRefName());
                            indexNameSet.add(c.getRefIndex().getName());
                        }

                        constraintNameSet.add(constraint.getName());

                        if (constraint.getConstraintType()
                                == SchemaObjectConstraintTypes.UNIQUE)
                        {
                            indexNameSet.add(constraint.getMainIndex().getName());
                        }

                        Table tn = table.moveDefinition(session, table.tableType,
                                                        null, null, null, -1, 0,
                                                        constraintNameSet,
                                                        indexNameSet);

                        tn.moveData(session, table, -1, 0);

                        tableSet = makeNewTables(tableSet, constraintNameSet,
                                                 indexNameSet);

                        if (constraint.getConstraintType()
                                == SchemaObjectConstraintTypes.PRIMARY_KEY)
                        {
                            int[] cols = constraint.getMainColumns();

                            for (int i = 0; i < cols.Length; i++)
                            {
                                tn.getColumn(cols[i]).setPrimaryKey(false);
                                tn.setColumnTypeVars(cols[i]);
                            }
                        }

                        //
                        database.schemaManager.removeSchemaObjects(constraintNameSet);
                        setNewTableInSchema(tn);
                        setNewTablesInSchema(tableSet);
                        updateConstraints(tn, emptySet);
                        updateConstraints(tableSet, constraintNameSet);
                        database.persistentStoreCollection.releaseStore(table);
                        database.schemaManager.recompileDependentObjects(tableSet);
                        database.schemaManager.recompileDependentObjects(tn);

                        table = tn;

                        // handle cascadingConstraints and cascadingTables
                        break;
                    }
                case SchemaObjectConstraintTypes.FOREIGN_KEY:
                    {
                        OrderedHashSet constraints = new OrderedHashSet();
                        Table mainTable = constraint.getMain();
                        QNameManager.QName mainName = constraint.getMainName();

                        constraints.add(mainName);
                        constraints.add(constraint.getRefName());

                        OrderedHashSet indexes = new OrderedHashSet();

                        indexes.add(constraint.getRefIndex().getName());

                        Table tn = table.moveDefinition(session, table.tableType,
                                                        null, null, null, -1, 0,
                                                        constraints, indexes);

                        tn.moveData(session, table, -1, 0);

                        //
                        database.schemaManager.removeSchemaObject(
                            constraint.getName());
                        setNewTableInSchema(tn);

                        // if constraint references same table, nothing changes
                        mainTable.removeConstraint(mainName.name);
                        updateConstraints(tn, emptySet);
                        database.persistentStoreCollection.releaseStore(table);
                        database.schemaManager.recompileDependentObjects(table);

                        table = tn;

                        break;
                    }
                case SchemaObjectConstraintTypes.CHECK:
                    database.schemaManager.removeSchemaObject(
                        constraint.getName());

                    if (constraint.isNotNull())
                    {
                        ColumnSchema column =
                            table.getColumn(constraint.notNullColumnIndex);

                        column.setNullable(false);
                        table.setColumnTypeVars(constraint.notNullColumnIndex);
                    }
                    break;
            }
        }

        /**
         * Allows changing the type or addition of an IDENTITY sequence.
         *
         * @param oldCol Column
         * @param newCol Column
         */
        public void retypeColumn(ColumnSchema oldCol, ColumnSchema newCol)
        {

            bool allowed = true;
            int oldType = oldCol.getDataType().typeCode;
            int newType = newCol.getDataType().typeCode;

            if (!table.isEmpty(session) && oldType != newType)
            {
                allowed =
                    newCol.getDataType().canConvertFrom(oldCol.getDataType());

                switch (oldType)
                {

                    case Types.SQL_BLOB:
                    case Types.SQL_CLOB:
                    case Types.OTHER:
                    case Types.CSHARP_OBJECT:
                        allowed = false;
                        break;
                }
            }

            if (!allowed)
            {
                throw Error.error(ErrorCode.X_42561);
            }

            int colIndex = table.getColumnIndex(oldCol.getName().name);

            // if there is a multi-column PK, do not change the PK attributes
            if (newCol.isIdentity() && table.hasIdentityColumn()
                    && table.identityColumn != colIndex)
            {
                throw Error.error(ErrorCode.X_42525);
            }

            if (table.getPrimaryKey().Length > 1)
            {
                newCol.setPrimaryKey(oldCol.isPrimaryKey());

                if (ArrayUtil.find(table.getPrimaryKey(), colIndex) != -1) { }
            }
            else if (table.hasPrimaryKey())
            {
                if (oldCol.isPrimaryKey())
                {
                    newCol.setPrimaryKey(true);
                }
                else if (newCol.isPrimaryKey())
                {
                    throw Error.error(ErrorCode.X_42532);
                }
            }
            else if (newCol.isPrimaryKey())
            {
                throw Error.error(ErrorCode.X_42530);
            }

            // apply and return if only metadata change is required
            bool meta = newType == oldType;

            meta &= oldCol.isNullable() == newCol.isNullable();
            meta &= oldCol.getDataType().scale == newCol.getDataType().scale;
            meta &= (oldCol.isIdentity() == newCol.isIdentity());
            meta &=
                (oldCol.getDataType().precision == newCol.getDataType().precision
                 || (oldCol.getDataType().precision
                     < newCol.getDataType().precision && (oldType
                         == Types.SQL_VARCHAR || oldType == Types.SQL_VARBINARY)));

            if (meta)
            {

                // size of some types may be increased with this command
                // default expressions can change
                oldCol.setType(newCol);
                oldCol.setDefaultExpression(newCol.getDefaultExpression());

                if (newCol.isIdentity())
                {
                    oldCol.setIdentity(newCol.getIdentitySequence());
                }

                table.setColumnTypeVars(colIndex);
                table.resetDefaultsFlag();

                return;
            }

            database.schemaManager.checkColumnIsReferenced(table.getName(),
                    table.getColumn(colIndex).getName());
            table.checkColumnInCheckConstraint(colIndex);
            table.checkColumnInFKConstraint(colIndex);
            checkConvertColDataType(oldCol, newCol);
            retypeColumn(newCol, colIndex);
        }

        /**
         *
         * @param oldCol Column
         * @param newCol Column
         */
        public void checkConvertColDataType(ColumnSchema oldCol, ColumnSchema newCol)
        {

            int colIndex = table.getColumnIndex(oldCol.getName().name);
            RowIterator it = table.rowIterator(session);

            while (it.hasNext())
            {
                Row row = it.getNextRow();
                Object o = row.getData()[colIndex];

                newCol.getDataType().convertToType(session, o,
                                                   oldCol.getDataType());
            }
        }

        /**
         *
         * @param column Column
         * @param colIndex int
         */
        public void retypeColumn(ColumnSchema column, int colIndex)
        {

            if (table.isText() && !table.isEmpty(session))
            {
                throw Error.error(ErrorCode.X_S0521);
            }

            Table tn = table.moveDefinition(session, table.tableType, column,
                                            null, null, colIndex, 0, emptySet,
                                            emptySet);

            tn.moveData(session, table, colIndex, 0);
            updateConstraints(tn, emptySet);
            setNewTableInSchema(tn);
            database.persistentStoreCollection.releaseStore(table);
            database.schemaManager.recompileDependentObjects(table);

            table = tn;
        }

        /**
         * performs the work for changing the nullability of a column
         *
         * @param column Column
         * @param nullable bool
         */
        public void setColNullability(ColumnSchema column, bool nullable)
        {

            Constraint c = null;
            int colIndex = table.getColumnIndex(column.getName().name);

            if (column.isNullable() == nullable)
            {
                return;
            }

            if (nullable)
            {
                if (column.isPrimaryKey())
                {
                    throw Error.error(ErrorCode.X_42526);
                }

                table.checkColumnInFKConstraint(
                    colIndex, SchemaObjectReferentialAction.SET_NULL);
                removeColumnNotNullConstraints(colIndex);
            }
            else
            {
                QNameManager.QName constName = database.nameManager.newAutoName("CT",
                    table.getSchemaName(), table.getName(),
                    SchemaObjectTypes.CONSTRAINT);

                c = new Constraint(constName, null,
                                   SchemaObjectConstraintTypes.CHECK);
                c.check = new ExpressionLogical(column);

                c.prepareCheckConstraint(session, table, true);
                column.setNullable(false);
                table.addConstraint(c);
                table.setColumnTypeVars(colIndex);
                database.schemaManager.addSchemaObject(c);
            }
        }

        /**
         * performs the work for changing the default value of a column
         *
         * @param colIndex int
         * @param def Expression
         */
        public void setColDefaultExpression(int colIndex, Expression def)
        {

            if (def == null)
            {
                table.checkColumnInFKConstraint(
                    colIndex, SchemaObjectReferentialAction.SET_DEFAULT);
            }

            table.setDefaultExpression(colIndex, def);
        }

        /**
         * Changes the type of a table
         *
         * @param session Session
         * @param newType int
         * @return bool
         */
        public bool setTableType(Session session, int newType)
        {

            int currentType = table.getTableType();

            if (currentType == newType)
            {
                return false;
            }

            switch (newType)
            {

                case TableBase.CACHED_TABLE:
                    break;

                case TableBase.MEMORY_TABLE:
                    break;

                default:
                    return false;
            }

            Table tn;

            try
            {
                tn = table.moveDefinition(session, newType, null, null, null, -1,
                                          0, emptySet, emptySet);

                tn.moveData(session, table, -1, 0);
                updateConstraints(tn, emptySet);
            }
            catch (CoreException )
            {
                return false;
            }

            setNewTableInSchema(tn);
            database.persistentStoreCollection.releaseStore(table);

            table = tn;

            database.schemaManager.recompileDependentObjects(table);

            return true;
        }

        public void setNewTablesInSchema(OrderedHashSet tableSet)
        {

            for (int i = 0; i < tableSet.size(); i++)
            {
                Table t = (Table)tableSet.get(i);

                setNewTableInSchema(t);
            }
        }

        public void setNewTableInSchema(Table newTable)
        {

            int i = database.schemaManager.getTableIndex(newTable);

            if (i != -1)
            {
                database.schemaManager.setTable(i, newTable);
            }
        }

        public void removeColumnNotNullConstraints(int colIndex)
        {

            for (int i = table.constraintList.Length - 1; i >= 0; i--)
            {
                Constraint c = table.constraintList[i];

                if (c.isNotNull())
                {
                    if (c.notNullColumnIndex == colIndex)
                    {
                        database.schemaManager.removeSchemaObject(c.getName());
                    }
                }
            }

            ColumnSchema column = table.getColumn(colIndex);

            column.setNullable(true);
            table.setColumnTypeVars(colIndex);
        }
    }
}
